package com.gusto.engine.semsim.measures.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import org.apache.log4j.Logger;

import com.gusto.engine.beans.model.Res;
import com.gusto.engine.semsim.exceptions.SimilarityException;
import com.gusto.engine.semsim.measures.ResourceSimilarity;
import com.gusto.engine.semsim.measures.ValueSimilarity;

/**
 * <p>{@code MatrixSimilarity} allows declarative similarity description in a matrix.
 * <pre>
 *    |  R    T    Y  
 *  --+---------------
 *  R |  x   0.6      
 *  T | 0.2   x   0.5 
 *  Y | 0.8        x  
 * </pre>
 * You can declare only some similarities. 
 * Similarity between identical resources is equal to {@literal 1.0}, 
 * and not defined similarities mean {@literal 0.0}.<br/>
 * Items that are defined in Stop-word list are not similar to themselves: similarity = {@literal 0.0}.<br/>
 * Stop-words are only considered in the case of identity.</p>
 * 
 * @author amokrane.belloui@gmail.com
 */
public class MatrixSimilarity implements ValueSimilarity, ResourceSimilarity {
	
	private Logger log = Logger.getLogger(MatrixSimilarity.class);
	
	private List<String> stopwords = new ArrayList<String>();
	public void setStopwords(List<String> stopwords) {
		this.stopwords = stopwords;
		log.debug(stopwords);
	}
	
	private String prefix = "";
	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}
	
	private List<MatrixSimilarityEntry> entries = new ArrayList<MatrixSimilarityEntry>();
	public void setEntries(List<MatrixSimilarityEntry> entries) {
		this.entries = entries;
	}
	
	private String entriesFileSeparator = "|";
	public void setEntriesFileSeparator(String entriesFileSeparator) {
		this.entriesFileSeparator = entriesFileSeparator;
	}
	
	protected String entriesFile;
	public void setEntriesFile(String entriesFile) throws SimilarityException {
		this.entriesFile = entriesFile;
		
		InputStream content = null;
		Properties props = new Properties();
		if (entriesFile.startsWith("classpath:")) {
			content = getClass().getResourceAsStream("/" + entriesFile.substring(10));
			if (content == null) {
				throw new SimilarityException("Classpath resource " + "/" + entriesFile.substring(11) + " not found");
			}
		}
		else if (entriesFile.startsWith("file:")) {
			try {
				content = new FileInputStream(new File(entriesFile.substring(5)));
			} 
			catch (FileNotFoundException e) {
				content = null;
			}
		}
		else {
			try {
				content = new FileInputStream(new File(entriesFile));
			} 
			catch (FileNotFoundException e) {
				content = null;
			}
		}
		
		try {
			props.load(content);
			log.debug("Properties file " + this.entriesFile + " loaded");
		} 
		catch (IOException e) {
			log.fatal("Error in loading properties file " + this.entriesFile);
			throw new SimilarityException("Impossible to load properties", e);
		}
		
		entries = new ArrayList<MatrixSimilarityEntry>();
		Enumeration<Object> enm = props.keys();
		while (enm.hasMoreElements()) {
			Object p = enm.nextElement();
			try {
				String key = p.toString();
				String value1 = key.substring(0, key.indexOf(entriesFileSeparator));
				String value2 = key.substring(key.indexOf(entriesFileSeparator) + entriesFileSeparator.length());
				Double sim = Double.parseDouble(props.getProperty(key).toString());
				entries.add(new MatrixSimilarityEntry(value1, value2, sim));
			}
			catch (Exception ex) {
				log.fatal("Error in config file " + this.entriesFile);
				throw new SimilarityException("Error in config : " + entriesFile);
			}
		}
		
	}
	
	public String getId() {
		String id = "[" + getClass().getName() + "|" + 
		        prefix + "|" + stopwords + "|";
		id += entries.size();
		id += "]"; 
		return id;
	}
	
	// EXTRA SIMILARITY : Matrix Similarity, add a DB config
	
	public Double getSimilarity(Object value1, Object value2) throws SimilarityException {
		// Load Matrix from File or Table
		// Return the correct value
		if (value1.equals(value2)) {
			if (!stopwords.contains(value1)) {
				log.warn("Identical " + value1 + " " + value2);
				return 1.0;
			}
			else {
				log.debug("Identical but stop word " + value1 + " " + value2);
				return 0.0;
			}
		}
		else {
			for (MatrixSimilarityEntry entry : entries) {
				if ((prefix+entry.getValue1()).equals(value1) && 
					(prefix+entry.getValue2()).equals(value2)) {
					log.info("Distance " + value1 + " " + value2 + " " + entry.getSimilarity());
					return entry.getSimilarity();
				}
				else if ((prefix+entry.getValue1()).equals(value2) && 
						(prefix+entry.getValue2()).equals(value1)) {
					log.info("Distance " + value1 + " " + value2 + " " + entry.getSimilarity());
					return entry.getSimilarity();
				}
			}
		}
		return 0.0;
	}
	
	public Double getSimilarity(Res value1, Res value2) throws SimilarityException {
		if (value1.equals(value2)) {
			if (!stopwords.contains(value1.toString())) {
				log.warn("Identical " + value1 + " " + value2);
				return 1.0;
			}
			else {
				log.debug("Identical but stop word " + value1 + " " + value2);
			}
		}
		else {
			for (MatrixSimilarityEntry entry : entries) {
				if ((prefix+entry.getValue1()).equals(value1) && 
						(prefix+entry.getValue2()).equals(value2)) {
					log.info("Distance " + value1 + " " + value2 + " " + entry.getSimilarity());
					return entry.getSimilarity();
				}
				else if ((prefix+entry.getValue1()).equals(value2) && 
						 (prefix+entry.getValue2()).equals(value1)) {
					log.info("Distance " + value1 + " " + value2 + " " + entry.getSimilarity());
					return entry.getSimilarity();
				}
			}
		}
		return 0.0;
	}
	
}
